/*
 * TouchLib.cpp
 *
 * @date 01.09.2014
 * @author Armin Joachimsmeyer
 *      Email:   armin.joachimsmeyer@gmail.com
 *      License: GPL v3 (http://www.gnu.org/licenses/gpl.html)
 * @version 1.0.0
 */

#include <Arduino.h>
#include "TouchLib.h"
#include "BlueDisplay.h"
#include "BlueSerial.h"
#include "TouchButton.h"
#include "TouchSlider.h"

#ifdef LOCAL_DISPLAY_EXISTS
#include "ADS7846.h"
ADS7846 TouchPanel;
#endif

struct XYPosition sDownPosition;
struct XYPosition sActualPosition;
struct XYPosition sUpPosition;

#ifdef LOCAL_DISPLAY_EXISTS
struct BluetoothEvent localTouchDownEvent;
struct BluetoothEvent localTouchEvent;
#endif
struct BluetoothEvent remoteTouchDownEvent; // to avoid overwriting of touch down events if CPU is busy and interrupt in not enabled
struct BluetoothEvent remoteTouchEvent;

bool sTouchIsStillDown = false;

void (*sSwipeEndCallback)(SWIPE_INFO const) = NULL;

void (*sConnectCallback)(struct XYSize * const) = NULL;
void (*sSimpleConnectCallback)(void) = NULL;
void (*sResizeCallback)(struct XYSize * const) = NULL;
void (*sSimpleResizeCallback)(void) = NULL;
// returns true if object was touched i.e callback was executed
void (*sTouchDownCallback)(struct XYPosition * const) = NULL;
void (*sTouchMoveCallback)(struct XYPosition * const) = NULL;

/*
 * helper variables
 */bool sButtonTouched = false; // flag if autorepeat button was touched - to influence long button press handling
bool sAutorepeatButtonTouched = false; // flag if autorepeat button was touched - to influence long button press handling
bool sSliderTouched = false; // flag if slider was touched - to influence long button press handling
bool sNothingTouched = false; // = !(sSliderTouched || sButtonTouched || sAutorepeatButtonTouched)
bool sSliderIsMoveTarget = false; // true if slider was touched by DOWN event

volatile bool sDisableTouchUpOnce = false; // set normally by application if long touch action was made

/**
 * Is called by thread main loops
 * Checks also for reconnect and resize events
 */
//
bool checkAndHandleTouchEvents(void) {
    resetTouchFlags();
#ifdef USE_SIMPLE_SERIAL
    if (remoteTouchDownEvent.EventType != EVENT_TAG_NO_EVENT) {
        handleEvent(&remoteTouchDownEvent);
        remoteTouchDownEvent.EventType = EVENT_TAG_NO_EVENT;
    }
    if (remoteTouchEvent.EventType != EVENT_TAG_NO_EVENT) {
        handleEvent(&remoteTouchEvent);
        remoteTouchEvent.EventType = EVENT_TAG_NO_EVENT;
    }

#else
    serialEvent();
#endif

#ifdef LOCAL_DISPLAY_EXISTS
    /*
     * then check local touch events
     */
    // TODO implement filling of event
    //    if (localTouchDownEvent.EventType != EVENT_TAG_NO_EVENT) {
    //        handleEvent(&localTouchDownEvent);
    //        localTouchDownEvent.EventType = EVENT_TAG_NO_EVENT;
    //    if (localTouchEvent.EventType != EVENT_TAG_NO_EVENT) {
    //        handleEvent(&localTouchEvent);
    //        localTouchEvent.EventType = EVENT_TAG_NO_EVENT;
//    }
#endif
    return false;
}

void registerConnectCallback(void (*aConnectCallback)(struct XYSize * const aMaxSizePtr)) {
    sConnectCallback = aConnectCallback;
}

void registerSimpleConnectCallback(void (*aConnectCallback)(void)) {
    sSimpleConnectCallback = aConnectCallback;
}

void registerResizeCallback(void (*aResizeCallback)(struct XYSize * const aActualSizePtr)) {
    sResizeCallback = aResizeCallback;
}

void registerSimpleResizeCallback(void (*aSimpleResizeCallback)(void)) {
    sSimpleResizeCallback = aSimpleResizeCallback;
}

void registerTouchDownCallback(void (*aTouchDownCallback)(struct XYPosition * const aActualSizePtr)) {
    sTouchDownCallback = aTouchDownCallback;
}

void registerSwipeEndCallback(void (*aSwipeEndCallback)(SWIPE_INFO const)) {
    sSwipeEndCallback = aSwipeEndCallback;
}

void registerTouchMoveCallback(void (*aTouchMoveCallback)(struct XYPosition * const aActualSizePtr)) {
    sTouchMoveCallback = aTouchMoveCallback;
}

/**
 * Maintain long and end callback, handle sDisableEndTouchOnce flag and compute swipe info
 */
void handleTouchUp(void) {
    if (sDisableTouchUpOnce) {
        sDisableTouchUpOnce = false;
        return;
    }
    if (sSwipeEndCallback != NULL) {
        if (abs(sDownPosition.PosX - sActualPosition.PosX) >= TOUCH_SWIPE_THRESHOLD
                || abs(sDownPosition.PosY - sActualPosition.PosY) >= TOUCH_SWIPE_THRESHOLD) {
            /*
             * Swipe recognized here
             * compute SWIPE data and call callback handler
             */
            SWIPE_INFO tSwipeInfo;
            tSwipeInfo.TouchStartX = sDownPosition.PosX;
            tSwipeInfo.TouchStartY = sDownPosition.PosY;
            tSwipeInfo.TouchDeltaX = sUpPosition.PosX - sDownPosition.PosX;
            tSwipeInfo.TouchDeltaXAbs = abs(tSwipeInfo.TouchDeltaX );
            tSwipeInfo.TouchDeltaY = sUpPosition.PosY - sDownPosition.PosY;
            tSwipeInfo.TouchDeltaYAbs = abs(tSwipeInfo.TouchDeltaY );
            if (tSwipeInfo.TouchDeltaXAbs >= tSwipeInfo.TouchDeltaYAbs) {
                // X direction
                tSwipeInfo.SwipeMainDirectionIsX = true;
                tSwipeInfo.TouchDeltaMax = tSwipeInfo.TouchDeltaX;
                tSwipeInfo.TouchDeltaAbsMax = tSwipeInfo.TouchDeltaXAbs;
            } else {
                tSwipeInfo.SwipeMainDirectionIsX = false;
                tSwipeInfo.TouchDeltaMax = tSwipeInfo.TouchDeltaY;
                tSwipeInfo.TouchDeltaAbsMax = tSwipeInfo.TouchDeltaYAbs;
            }
            sSwipeEndCallback(tSwipeInfo);

        } else if (sTouchDownCallback != NULL) {
            /*
             * no swipe -> take it as regular touch
             */
            sTouchDownCallback(&sActualPosition);
        }
    }
}

/**
 * Interprets the event type and manage the flags
 *
 * (Touch) Message has 7 bytes:
 * Gross message length in bytes
 * Function code
 * X Position LSB
 * X Position MSB
 * Y Position LSB
 * Y Position MSB
 * Sync Token
 */
void handleEvent(struct BluetoothEvent * aEvent) {
    uint8_t tEventType = aEvent->EventType;
    if (tEventType == EVENT_TAG_TOUCH_ACTION_DOWN) {
        // must initialize all positions here!
        sDownPosition = aEvent->EventData.TouchPosition;
        sActualPosition = aEvent->EventData.TouchPosition;
        sTouchIsStillDown = true;
        if (sTouchDownCallback != NULL) {
            sTouchDownCallback(&aEvent->EventData.TouchPosition);
        }
    } else if (tEventType == EVENT_TAG_TOUCH_ACTION_MOVE) {
        if (sTouchMoveCallback != NULL) {
            sTouchMoveCallback(&aEvent->EventData.TouchPosition);
        }
        sActualPosition = aEvent->EventData.TouchPosition;

    } else if (tEventType == EVENT_TAG_TOUCH_ACTION_UP) {
        sUpPosition = aEvent->EventData.TouchPosition;
        if (sTouchIsStillDown) {
            // only call once even if multiple UP events happens
            sTouchIsStillDown = false;
            handleTouchUp();
            // must be reset after handleTouchUp()
            sSliderIsMoveTarget = false;
        }
    } else if (tEventType == EVENT_TAG_TOUCH_ACTION_ERROR) {
        // try to reset touch state
        sUpPosition = aEvent->EventData.TouchPosition;
        if (sTouchIsStillDown) {
            sTouchIsStillDown = false;
            handleTouchUp();
        }
    } else if (tEventType == EVENT_TAG_BUTTON_CALLBACK_ACTION) {
        void (*tCallback)(uint16_t, int16_t);
        tCallback = aEvent->EventData.CallbackInfo.OnTouchHandler;
        tCallback(aEvent->EventData.CallbackInfo.ButtonIndex, aEvent->EventData.CallbackInfo.ButtonValue);
    } else if (tEventType == EVENT_TAG_CONNECTION_BUILD_UP) {
        BlueDisplay1.setMaxDisplaySize(&aEvent->EventData.DisplaySize);
        if (sSimpleConnectCallback != NULL) {
            sSimpleConnectCallback();
        } else if (sConnectCallback != NULL) {
            sConnectCallback(&aEvent->EventData.DisplaySize);
        }
        // also handle as resize
        tEventType = EVENT_TAG_RESIZE_ACTION;
    }
    if (tEventType == EVENT_TAG_RESIZE_ACTION) {
        BlueDisplay1.setActualDisplaySize(&aEvent->EventData.DisplaySize);
        if (sSimpleResizeCallback != NULL) {
            sSimpleResizeCallback();
        } else if (sResizeCallback != NULL) {
            sResizeCallback(&aEvent->EventData.DisplaySize);
        }
    }

}

bool isTouchStillDown(void) {
    return sTouchIsStillDown;
}

void resetTouchFlags(void) {
    sButtonTouched = false;
    sAutorepeatButtonTouched = false;
    sSliderTouched = false;
    sNothingTouched = false;
}

/**
 *
 * @param aActualPositionPtr
 * @return
 */
void simpleTouchDownHandler(struct XYPosition * const aActualPositionPtr) {
    if (TouchSlider::checkAllSliders(aActualPositionPtr->PosX, aActualPositionPtr->PosY)) {
        sSliderIsMoveTarget = true;
    } else {
        if (!TouchButton::checkAllButtons(aActualPositionPtr->PosX, aActualPositionPtr->PosY)) {
            sNothingTouched = true;
        }
    }
}

void simpleTouchDownHandlerForSlider(struct XYPosition * const aActualPositionPtr) {
    if (TouchSlider::checkAllSliders(aActualPositionPtr->PosX, aActualPositionPtr->PosY)) {
        sSliderIsMoveTarget = true;
    }
}

void simpleTouchMoveHandlerForSlider(struct XYPosition * const aActualPositionPtr) {
    if (sSliderIsMoveTarget) {
        TouchSlider::checkAllSliders(aActualPositionPtr->PosX, aActualPositionPtr->PosY);
    }
}

